/**
Engine.cpp
Title: Binge Engine Program File
Descr: Engine Class

Copyright (c) 2010, Brant Ryan Anderson
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <ORGANIZATION> nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/

#include "../../binge/core/Engine.hpp"

namespace binge
{

Engine::Engine(void)
{
	srand((unsigned int)time(NULL));

	maximizeProcessor = false;
	frameCountCore = 0;
	frameRateCore = 0;
	frameCountReal = 0;
	frameRateReal = 0;
	engineFrameRateMS = 16;     //update with 60 FPS timing(16 = 62.5 max)
                                //update with 30 FPS timing(32 = 31.25 max)
	mouseVisible = true;
	gameMode = GAMEMODE_NONE;
	versionMajor = VERSION_MAJOR;
	versionMinor = VERSION_MINOR;

	//set default values
	setGameTitle("Binge Engine");
	setScreenWidth(800);
	setScreenHeight(600);
	setColorDepth(32);
	setFullscreen(false);

	audio = new Audio();
}

Engine::~Engine(void)
{
	delete audio;
	audio = 0;
}

std::string
Engine::getVersionText(void)
{
	std::ostringstream s;
	s << "Binge version " << versionMajor << "." << versionMinor;
	return s.str();
}

void
Engine::Message(std::string message, std::string title, int icon)
{
	if (this->getFullscreen())
	{
        sf::VideoMode vidMode = sf::VideoMode(this->getScreenWidth(), this->getScreenHeight(), this->getColorDepth());
        if (!vidMode.IsValid())
        {
            std::cout << title.c_str() << ": " << message.c_str() << std::endl;
            return;
        }
        unsigned long winStyle;
        if (this->getFullscreen())
        {
            winStyle = sf::Style::Fullscreen;
        } else {
            winStyle = sf::Style::Close;
        }
        this->screen.Create(vidMode, this->getGameTitle(), sf::Style::Close);
        this->screen.Show(false);
        this->dialog.showDialog(message.c_str(), title.c_str(), icon);
        this->screen.Create(vidMode, this->getGameTitle(), winStyle);
        this->screen.PreserveOpenGLStates(true);
        this->screen.ShowMouseCursor(mouseVisible);
        this->screen.Show(true);
	} else {
	    this->screen.Show(false);
        this->dialog.showDialog(message.c_str(), title.c_str(), icon);
        this->screen.Show(true);
	}
}

void
Engine::Fatal_Error(std::string message, std::string title)
{
	this->Message(message, title, wxICON_ERROR);
	Shutdown();
}

bool
Engine::Init_SFML(void)
{
    sf::VideoMode vidMode = sf::VideoMode(this->getScreenWidth(), this->getScreenHeight(), this->getColorDepth());
	if (!vidMode.IsValid())
	{
		return false;
	}
    unsigned long winStyle;
    if (this->getFullscreen())
    {
        winStyle = sf::Style::Fullscreen;
    } else {
        winStyle = sf::Style::Close;
    }
    this->screen.Create(vidMode, this->getGameTitle(), winStyle);
    this->screen.PreserveOpenGLStates(true);
    this->screen.ShowMouseCursor(mouseVisible);

	return true;
}

bool
Engine::Init_OpenGL(void)
{
	// viewport should cover the whole screen
	glViewport( 0, 0, getScreenWidth(), getScreenHeight() );

	// select the projection matrix
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();

	// Set up the orthographic projection matrix
	glOrtho( 0, getScreenWidth(), getScreenHeight(), 0, -1, 1 );
	// select the orthographic projection matrix
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();

	// set the smooth shading model
	glShadeModel( GL_SMOOTH );
	// set clear color to black
	glClearColor( 0, 0, 0, 1 );

	// Z-Buffer
	glEnable( GL_DEPTH_TEST );
	glDepthMask(GL_TRUE);
	// Depth function
	glDepthFunc( GL_LEQUAL );
	// Depth Buffer Setup
	glClearDepth( 1 );

	// Blending
	glEnable( GL_BLEND );
	// Blending function
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	// Enable Texture Mapping
	glEnable(GL_TEXTURE_2D);
	// Really Nice Perspective Calculations
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	//If there was any errors
	if( glGetError() != GL_NO_ERROR )
	{
		return false;
	}
	//If everything initialized
	return true;
}

bool
Engine::Init(void)
{
	//Initialize SFML
	if (Init_SFML() == false)
	{
	   this->Fatal_Error("Error initializing SFML!", "initSFML()");

	   return false;
	}
	// Initialize OpenGL
	if (Init_OpenGL() == false)
	{
	   this->Fatal_Error("Error initializing OpenGL!", "initOpenGL()");

	   return false;
	}
	// Initialize Audio
	if (audio->Init() == false)
	{
	    this->Message("Error initializing Audio.", "Audio Disabled");
		audio->disabled = true;
	}

	return true;
}

bool
Engine::Render_Start()
{
	screen.Clear();
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	return true;
}

bool
Engine::Render_Stop()
{
	screen.Display();

	return true;
}

void
Engine::Shutdown()
{
	gameover = true;
}

void
Engine::Update(	void (BaseGame::*game_update)(void),
				void (BaseGame::*game_render)(void),
				BaseGame* basegame)
{
	//calculate core framerate
	frameCountCore++;
	if (coreTimer.Stopwatch(999)) {

		frameRateCore = frameCountCore;
		frameCountCore = 0;
	}

	//fast update with no timing
	(basegame->*game_update)();

	//update entities with no timing
	if (getGameMode() != GAMEMODE_PAUSED)
	{
		UpdateEntities(&BaseGame::gameEntityUpdated, basegame);
	}

	//perform global collision testing at 20 FPS
	if (getGameMode() != GAMEMODE_PAUSED && collisionTimer.Stopwatch(50))
	{
		TestForCollisions(&BaseGame::gameEntityCollision, basegame);
	}

	//update with 60 FPS timing(16 = 62.5 max)
	//update with 30 FPS timing because of performance issues(32 = 31.25 max)
	if (!timedUpdate.Stopwatch(engineFrameRateMS)) {
		if (!this->getMaximizeProcessor())
		{
			usleep(1);
		}
	}
	else {
		//calculate real framerate
		frameCountReal++;
		if (realTimer.Stopwatch(999)) {

			frameRateReal = frameCountReal;
			frameCountReal = 0;
		}

		// If a music play list is being used, cycle through music play list
		if (audio->playlist)
		{
			if (!audio->Music_isPlaying() && !audio->Music_isPaused()) audio->Music_List_Next();
		}

		//begin rendering
		this->Render_Start();

		//render functions provided for entities
		if (getGameMode() != GAMEMODE_PAUSED)
		{
			DrawEntities(&BaseGame::gameEntityRendered, basegame);
		}
		//render basegame
		(basegame->*game_render)();

		//done rendering
		this->Render_Stop();
	}
	BuryEntities();
}

void
Engine::UpdateEntities(void (BaseGame::*game_entity_updated)(Entity* entity),
						BaseGame* basegame)
{
	std::list<Entity*>::iterator iter;
	Entity *entity;

    // SPRITES
	iter = entityList.begin();
	while (iter != entityList.end())
	{
		//point local sprite to object in the list
		entity = *iter;

		//is this entity alive and a sprite?
		if ( entity->getAlive() && entity->getObjectType() == OBJECTTYPE_SPRITE ) {

			//move/animate entity
			entity->move();
			entity->animate();

			//tell game that this entity has been updated
			(basegame->*game_entity_updated)(entity);

			//see if this entity will auto-expire
			if ( entity->getLifetime() > 0)
			{
				if ( entity->lifetimeExpired() )
				{
					entity->setAlive(false);
				}
			}
		}
		//is this entity alive and immobile?
		else if ( entity->getAlive() && entity->getVisible() && entity->getObjectType() == OBJECTTYPE_IMMOBILE ) {

			//animate entity
			entity->animate();

			//tell game that this entity has been updated
			(basegame->*game_entity_updated)(entity);

			//see if this entity will auto-expire
			if ( entity->getLifetime() > 0)
			{
				if ( entity->lifetimeExpired() )
				{
					entity->setAlive(false);
				}
			}
		}
		++iter;
	}
}

void
Engine::DrawEntities(	void (BaseGame::*game_entity_rendered)(Entity* entity),
						BaseGame* basegame)
{
	Entity *entity;
	std::list<Entity*>::iterator iter = entityList.begin();

	while (iter != entityList.end())
	{
		//temporary pointer
		entity = *iter;

		//is this entity in use?
		if ( entity->getAlive() && entity->getVisible() ) {
			entity->draw();
			// tell game that entity has been drawn
			(basegame->*game_entity_rendered)(entity);
		}

		++iter;
	}
}

void
Engine::BuryEntities()
{
	std::list<Entity*>::iterator iter;
	iter = entityList.begin();
	while (iter != entityList.end())
	{
		if ( (*iter)->getAlive() == false )
		{
			delete (*iter);
			(*iter) = 0;
			iter = entityList.erase(iter);
		}
		else iter++;
	}
}

void
Engine::addEntity(Entity *entity)
{
	static int id = 0;
	entity->setID(id);
	entityList.push_back(entity);
	id++;
}

Entity*
Engine::FindEntity(std::string name)
{
	std::list<Entity*>::iterator i = entityList.begin();
	while (i != entityList.end())
	{
		if ( (*i)->getAlive() == true && (*i)->getName() == name )
			return *i;
		else
			++i;
	}

	return 0;
}

Entity*
Engine::FindEntity(int objectType)
{
	std::list<Entity*>::iterator i = entityList.begin();
	while (i != entityList.end())
	{
		if ( (*i)->getAlive() == true && (*i)->getObjectType() == objectType )
			return *i;
		else
			++i;
	}
	return 0;
}

int
Engine::getEntityCount(int objectType)
{
	int total = 0;
	std::list<Entity*>::iterator i = entityList.begin();
	while (i != entityList.end())
	{
		if ( (*i)->getAlive() == true && (*i)->getObjectType() == objectType )
			total++;
		else
			++i;
	}
	return total;
}

bool
Engine::Collision(Entity* entity1, Entity* entity2)
{
	switch (entity1->getCollisionMethod()) {
		case COLLISION_RECT:
			return CollisionBR(entity1,entity2);
			break;
		case COLLISION_DIST:
			return CollisionD(entity1,entity2);
			break;
		case COLLISION_NONE:
		default:
			return false;
	}
}

bool
Engine::CollisionBR(Entity* entity1, Entity* entity2)
{
	bool ret = false;
	sf::Rect<float> ra;
	sf::Rect<float> rb;
	ra = entity1->collisionRect;
	rb = entity2->collisionRect;

	//are any of entity b's corners intersecting entity a?
	if (ra.Intersects(rb)) ret = true;

	return ret;
}

bool
Engine::CollisionD(Entity* entity1, Entity* entity2)
{
	double radius1, radius2;

	//calculate radius 1
	if (entity1->collisionRect.Right > entity1->collisionRect.Bottom)
		radius1 = (entity1->getColRectWidth()*entity1->getScale())/2;
	else
		radius1 = (entity1->getColRectHeight()*entity1->getScale())/2;

	//point = center of entity 1
	double x1 = entity1->collisionRect.Left + radius1;
	double y1 = entity1->collisionRect.Top + radius1;
	Vector3 vector1(x1, y1, 0.0);

	//calculate radius 2
	if (entity2->collisionRect.Right > entity2->collisionRect.Bottom)
		radius2 = (entity2->getColRectWidth()*entity2->getScale())/2;
	else
		radius2 = (entity2->getColRectHeight()*entity2->getScale())/2;

	//point = center of entity 2
	double x2 = entity2->collisionRect.Left + radius2;
	double y2 = entity2->collisionRect.Top + radius2;
	Vector3 vector2(x2, y2, 0.0);

	//calculate distance
	double dist = vector1.Distance( vector2 );

	//return distance comparison
	return (dist < radius1 + radius2);
}

void
Engine::TestForCollisions(	void (BaseGame::*game_entity_collision)(Entity* entity1, Entity* entity2),
							BaseGame* basegame)
{
	std::list<Entity*>::iterator first;
	std::list<Entity*>::iterator second;
	Entity* ent1;
	Entity* ent2;


	first = entityList.begin();
	while (first != entityList.end())
	{
		//point local entity to entity contained in the list
		ent1 = *first;

		if (ent1->getAlive() && ent1->getVisible() && ent1->isCollidable())
		{
			//test all other entities for collision
			second = entityList.begin();
			while (second != entityList.end())
			{
				//point local entity to entity contained in the list
				ent2 = *second;

				//if other entity is active and not same as first entity...
				if (ent2->getAlive() && ent2->getVisible() && ent2->isCollidable() && ent1 != ent2)
				{
					//test for collision
					if (Collision(ent1, ent2)) {

						//notify game of collision
						(basegame->*game_entity_collision)(ent1, ent2);

					}

				}
				//go to the next entity in the list
				second++;

			}

		}
		//go to the next entity in the list
		first++;

	} //while

}


void
Engine::Close(	void (BaseGame::*game_end)(void),
				BaseGame* basegame)
{
	(basegame->*game_end)();
	screen.Close();
}

} //namespace
